package com.jamiexu.lua.interpreter;

import com.jamiexu.lua.interpreter.exception.LexerException;
import com.jamiexu.lua.interpreter.exception.ParseException;
import com.jamiexu.lua.interpreter.token.Token;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

public class Lexer {
    private int pos;
    private int lineNo;
    private char currentChar;
    private final BufferedReader bufferedReader;
    private char[] linesCode;
    private final ArrayList<String> SYMBOLS_LIST = new ArrayList<>();
    private final String[] SYMBOLS =
            {
                    ">=", "<=", "~=", "<>", "==", "<", ">",
                    "<<", ">>", ">>>", "&", "|", "^", "~", "-", "+", "*", "/"
            };


    public Lexer(BufferedReader bufferedReader) {
        if (bufferedReader == null)
            error("BufferedReader point a null address...");
        this.pos = 0;
        this.lineNo = 1;
        this.bufferedReader = bufferedReader;
        try {
            String line;
            while ((line = this.bufferedReader.readLine()) != null && line.length() == 0)
                this.lineNo++;
            assert line != null;
            this.linesCode = line.toCharArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.currentChar = this.linesCode[this.pos];
        this.SYMBOLS_LIST.addAll(Arrays.asList(this.SYMBOLS));
    }

    private void next() throws LexerException {
        this.pos++;
        if (this.pos > this.linesCode.length - 1) {
            this.currentChar = 0;
        } else {
            this.currentChar = this.linesCode[this.pos];
        }
    }

    private String string(char quote) throws LexerException {
        StringBuilder stringBuilder = new StringBuilder();
        next();
        while (this.currentChar != 0 && this.currentChar != quote) {
            stringBuilder.append(this.currentChar);
            next();
        }
        next();
        return stringBuilder.toString();
    }

    private long integer() throws LexerException {
        StringBuilder stringBuilder = new StringBuilder();
        while (this.currentChar != 0 && Character.isDigit(this.currentChar)) {
            stringBuilder.append(this.currentChar);
            next();
        }
        return Long.parseLong(stringBuilder.toString());
    }

    private void skipSpaces() throws LexerException {
        while (this.currentChar != 0 && Character.isSpaceChar(this.currentChar)) {
            next();
        }
    }

    private Token symbol() throws LexerException {
        int pos = this.pos;
        StringBuilder stringBuilder = new StringBuilder();
        while (this.currentChar != 0 && (this.currentChar != ' ' && this.isAlpha(this.currentChar))) {
            stringBuilder.append(this.currentChar);
            next();
        }

        return new Token(Token.TYPE.SYMBOL, stringBuilder.toString(), this.lineNo, pos);
    }

    private boolean isAlpha(char c) {
        return Character.isDigit(c) || Character.isLetter(c) || c == 0x5F;
    }

    public Token getNextToken() throws LexerException, ParseException {
        while (this.currentChar != 0) {
            int pos = this.pos;
            if (this.currentChar == ' ') {
                skipSpaces();
            } else if (Character.isDigit(this.currentChar)) {
                return new Token(Token.TYPE.INTEGER, integer(), lineNo, pos);
            } else {
                switch (this.currentChar) {
                    case '"':
                    case '\'':
                        return new Token(Token.TYPE.STRING, string(this.currentChar), this.lineNo, pos);
                    default:
                        if (isAlpha(this.currentChar))
                            return symbol();
                        else {
                            Token token = new Token(Token.TYPE.OPERATOR, this.currentChar, this.lineNo, pos);
                            next();
                            return token;
                        }
                }
            }
        }

        String line = null;
        try {
            while ((line = this.bufferedReader.readLine()) != null && line.length() == 0)
                this.lineNo++;

        } catch (IOException e) {
            e.printStackTrace();
        }

        if (line != null) {
            this.pos = 0;
            this.lineNo++;
            this.linesCode = line.toCharArray();
            this.currentChar = this.linesCode[this.pos];
            return getNextToken();
        } else {
            return new Token(Token.TYPE.EOF, null, lineNo, 0);
        }


    }


    private void error(String message) throws LexerException {
        throw new LexerException(message);
    }


}
